package IC.SEMANTIC;

import IC.AST.*;
import IC.AST.PrimitiveType;
import IC.Types.*;


public class CreateTypeTableVisitor implements Visitor {
	
	private TypeTable typeTable;
	private SemanticError semanticError = null;
	
	public TypeTable getTypeTable() {
		return typeTable;
	}
	
	public SemanticError getSemanticError()	{
		return semanticError;
	}

	
	public CreateTypeTableVisitor(String icFilePath){
		typeTable=new TypeTable(icFilePath);
	}
	
	public Object visit(Program icProgram) { 
		for (ICClass icClass : icProgram.getClasses()) { /*iterate over all the classes in the program*/
			ClassType classType = this.typeTable.ClassToClassType(icClass);
			if (classType == null) { 
				if (this.typeTable.ClassNameToClassType(icClass.getName()) == null) {
					this.semanticError = new SemanticError(icClass.getLine(),"The class extends the type:"+icClass.getParentClassName()+" that wasn't defined");
				}
				else {
					this.semanticError = new SemanticError(icClass.getLine(), "The class:"+icClass.getParentClassName()+" was already defined");
				}
				return null;
			}
		}
		for (ICClass icClass: icProgram.getClasses()) { /*iterate over all the classes*/
			icClass.accept(this);
		}
		return null;
	}

	
	
	
	public Object visit(ICClass icClass) {
		
		/*visit in the class fields*/
		for (Field field:icClass.getFields()) { /*visit in all the ast nodes of fields*/
			if ((IC.Types.Type)field.accept(this) == null) {
				return null;
			}
		}
		/*visit in the class methods*/
		for (Method method : icClass.getMethods()) {
			if ((MethodType)method.accept(this)==null) {
				break;			
			}
		}
		return null;
	}
	
	
	public Object visit(Field icField) {
		return icField.getType().accept(this);
	}
	
	
	
	private MethodType GenericMethodVisitor(Method icMethod) {
		MethodType methodType = null;
		int i;
		int formalsCount;
		IC.Types.Type returnType = (IC.Types.Type)icMethod.getType().accept(this);
		if (returnType!=null) {
			formalsCount= icMethod.getFormals().size(); /*The number of formals(type and name) in the method*/
			IC.Types.Type[] parametersTypeArray = new IC.Types.Type[formalsCount];
			for (i=0 ;i<parametersTypeArray.length; i++) {
				parametersTypeArray[i] = (IC.Types.Type)icMethod.getFormals().get(i).accept(this);
				if (parametersTypeArray[i]==null) { 
					return methodType;
				}
			}
			
			/*handle the statements*/
			for (Statement statement : icMethod.getStatements()) {
				statement.accept(this);
				if (semanticError!=null) {
					return methodType;
				}
			}
			methodType = typeTable.getMethodType(parametersTypeArray, returnType);
			icMethod.setSemanticType(methodType); 
		}
		return methodType;	
	}
	
	
	public Object visit(VirtualMethod icVirtualMethod) {
		return GenericMethodVisitor(icVirtualMethod);
	}

	
	public Object visit(StaticMethod icStaticMethod) {
		return GenericMethodVisitor(icStaticMethod);
	}

	
	public Object visit(LibraryMethod icLibraryMethod) {
		return GenericMethodVisitor(icLibraryMethod);
	}

	
	public Object visit(Formal icFormal) {
		return icFormal.getType().accept(this);
	}

	
	public Object visit(PrimitiveType type) {
		IC.Types.Type result = null;
		switch (type.getType()) {
		case VOID:
			result = typeTable.getVoidPrimitiveType();
			break;
		case STRING:
			result = typeTable.getStringPrimitiveType();
			break;				
		case INT:
			result = typeTable.getIntegerPrimitiveType();
			break;
		case BOOLEAN:
			result = typeTable.getBoolPrimitiveType();
			break;
		}
		result = typeTable.getArrayType(result, type.getDimension());
		type.setSemanticType(result);
		return result;
	}

	
	public Object visit(UserType icUserType) {
		IC.Types.Type userType = typeTable.ClassNameToClassType(icUserType.getName());
		if (userType==null) {
			semanticError = new SemanticError(icUserType.getLine(), "The type:"+icUserType.getName()+" cannot be resolved");
		}
		else{
			userType = typeTable.getArrayType(userType, icUserType.getDimension());
			icUserType.setSemanticType(userType);
		}
		return userType;
	}

	
	public Object visit(Assignment icAssignment) {
		icAssignment.getVariable().accept(this);
		if (semanticError==null) {
			icAssignment.getAssignment().accept(this);
		}
		return null;
	}

	
	public Object visit(CallStatement icCallStatement) {
		return icCallStatement.getCall().accept(this);
	}

	
	public Object visit(Return icReturn) {
		if (icReturn.hasValue()) {
			return icReturn.getValue().accept(this);
		}
		return null;
	}

	
	public Object visit(If ifStatement) {
		
		/*check the condition of the ifstatement*/
		ifStatement.getCondition().accept(this);
		if (semanticError==null) {
			ifStatement.getOperation().accept(this);
			if (semanticError==null) {
				if (ifStatement.hasElse()) {
					ifStatement.getElseOperation().accept(this);
				}
			}
		}
		return null;
		
		
	}

	
	public Object visit(While icWhile) {
		/*check the condition of the while*/
		icWhile.getCondition().accept(this);
		if (semanticError==null) {
			icWhile.getOperation().accept(this);
		}
		return null;
	}

	
	public Object visit(Break icBreak) {
		return null;
	}

	
	public Object visit(Continue icContinue) {
		return null;
	}

	
	public Object visit(StatementsBlock icStatementsBlock) {
		for (Statement statement :icStatementsBlock.getStatements()) {
			statement.accept(this);
			if (semanticError!=null) {
				break;
			}
		}
		return null;
	}

	
	public Object visit(LocalVariable icLocalVariable) {
		icLocalVariable.getType().accept(this);
		if (semanticError==null && icLocalVariable.hasInitValue()) {
			icLocalVariable.getInitValue().accept(this);
		}
		return null;
	}

	
	public Object visit(VariableLocation icVariableLocation) {
		if (icVariableLocation.isExternal()) {
			icVariableLocation.getLocation().accept(this);
		}
		return null;
	}

	
	public Object visit(ArrayLocation icArrayLocation) {
		icArrayLocation.getArray().accept(this);
		if (semanticError==null) {
			icArrayLocation.getIndex().accept(this);
		}
		return null;	
	}

	
	private void visitCall(Call icCall) {
		for (Expression argumentExpression : icCall.getArguments()) {
			argumentExpression.accept(this);
			if (semanticError!=null) {
				break;
			}
		}
	}
	
	
	public Object visit(StaticCall icStaticCall) {
		visitCall(icStaticCall);
		if (semanticError==null) {
			ClassType classType = typeTable.ClassNameToClassType(icStaticCall.getClassName());
			if (classType==null) {
				semanticError = new SemanticError(icStaticCall.getLine(), "The static method's class:"+icStaticCall.getClassName()+" is undefined");
			}
			icStaticCall.setSemanticType(classType);
		}
		return null;
	}

	
	public Object visit(VirtualCall icVirtualCall) {
		visitCall(icVirtualCall);
		if (semanticError==null && icVirtualCall.isExternal()) {
			icVirtualCall.getLocation().accept(this);
		}
		return null;
	}

	
	public Object visit(This icThis) {
		return null;
	}

	
	public Object visit(NewClass icNewClass) {
		IC.Types.Type classType = typeTable.ClassNameToClassType(icNewClass.getName());
		if (classType == null) {
			semanticError = new SemanticError(icNewClass.getLine(), "Unable to create a new instance of the undefined type:"+icNewClass.getLine());
		}
		icNewClass.setSemanticType(classType);
		return null;
	}

	
	public Object visit(NewArray icNewArray) {
		icNewArray.getType().accept(this);
		if (semanticError==null) {
			icNewArray.getSize().accept(this);
			IC.Types.Type type = icNewArray.getType().getSemantictype();
			icNewArray.setSemanticType(typeTable.getArrayType(type));
		}
		return null;		
	}

	
	public Object visit(Length icLength) {
		icLength.getArray().accept(this);
		return null;
	}

	
	public Object visit(MathBinaryOp icMathBinaryOp) {
		icMathBinaryOp.getFirstOperand().accept(this);
		if (semanticError==null) {
			icMathBinaryOp.getSecondOperand().accept(this);
		}
		return null;
	}

	
	public Object visit(LogicalBinaryOp icLogicalBinaryOp) {
		icLogicalBinaryOp.getFirstOperand().accept(this);
		if (semanticError==null) {
			icLogicalBinaryOp.getSecondOperand().accept(this);
		}
		return null;
	}

	
	public Object visit(MathUnaryOp icMathUnaryOp) {
		icMathUnaryOp.getOperand().accept(this);
		return null;
	}

	
	public Object visit(LogicalUnaryOp icLogicalUnaryOp) {
		icLogicalUnaryOp.getOperand().accept(this);
		return null;
	}

	
	public Object visit(Literal icLiteral) {
		return null;
	}

	
	public Object visit(ExpressionBlock icExpressionBlock) {
		return icExpressionBlock.getExpression().accept(this);
	}


}
